#!/usr/bin/env python
# encoding: utf-8
"""
@author: guihehans
@file: app.py
@time: 2020/5/20 13:33
@function:

"""
from gevent import monkey;

monkey.patch_all()

import sys
from datetime import timedelta, datetime
import time
import functools
import os
from json import dumps, loads
from flask import Flask, request, render_template, session, redirect, url_for, flash, json, jsonify, make_response
from werkzeug.exceptions import HTTPException, InternalServerError
from jenkins_tool.common import CrowdSSO, RETURN_MSG_INFO, const
from jenkins_tool.log.Logger import Logger
from jenkins_tool.service import jenkins_api, gittag_extract, zeus_api
from gevent.pool import Pool

from gevent.pywsgi import WSGIServer
from jinja2 import Environment, PackageLoader, select_autoescape

private_log = Logger(dir_path=const.Log_config_dir, file_name=const.Log_config_file_name)
app = Flask(__name__, static_folder='./jenkins_tool/static',
            template_folder='./jenkins_tool/templates')
app.secret_key = os.urandom(32)
app.config['SESSION_TYPE'] = 'filesystem'

crowd_client = CrowdSSO.CrowdClient()


@app.before_request
def before_request():
    """
    set the session time to live(ttl) to be 30 minutes

    :return:
    """
    session.permanent = True
    app.permanent_session_lifetime = timedelta(minutes=30)


@app.route('/', methods=['GET', 'POST'])
def index():
    return render_template('index.html')


@app.route('/login', methods=['GET', "POST"])
def login():
    if request.method == "GET":
        return render_template('/auth/login.html')
    else:
        username = request.form['username']
        password = request.form['password']
        token = crowd_client.login(username, password)
        if token is not None:
            session['username'] = username
            session['token'] = token
            return render_template('index.html')
        return render_template('/auth/login.html', message='Bad username or password')


@app.route('/logout', methods=['GET', 'POST'])
def logout():
    # remove the username from the session if it's there
    session.pop("username", None)
    session.pop("token", None)
    return redirect(url_for('index'))


"""
************************ Build phase related routes start ****************************************
"""


@app.route('/build/pre_build', methods=['GET'])
def pre_build():
    """
    render the input task number page.

    :return:
    """
    username = session.get("username")
    if username is None:
        return redirect(url_for('login'))
    else:
        return render_template('/build/pre_query_task_number.html')


@app.route("/build/pre_batch_build", methods=['GET'])
def query_pre_build():
    task_number = int(request.args.get("task_number"))
    git_data = gittag_extract.git_tag_build(task_number)
    return render_template('/build/pre_batch_build.html', task_number=task_number,
                           git_data=git_data.to_dict(orient='records'))


@app.route("/build/pre_batch_build", methods=["POST"])
def pre_batch_build():
    # check login
    username = session.get("username")
    token = session.get("token")
    if username is None or token is None:
        return redirect(url_for('login'))
    # user existed
    else:
        array_length = int(request.form['array_length'])
        task_number = int(request.form['task_number'])
        param_list = [{} for _ in range(array_length)]
        job_list = []
        jenkins_server = jenkins_api.connect_server(token, crowd_client)
        # batch submit jobs to jenkins
        for i in range(array_length):
            need_to_build = str(request.form["need_to_build" + "_" + str(i + 1)])
            print(need_to_build)
            # build the job when it's need to build,
            if need_to_build == "True":
                print(" ready to execute")
                param_list[i]['task_number'] = task_number
                param_list[i]['sys_id'] = request.form["sysid" + "_" + str(i + 1)]
                param_list[i]['gittag'] = request.form["gittag" + "_" + str(i + 1)]
                param_list[i]['opser'] = request.form["opser" + "_" + str(i + 1)]
                param_list[i]['memo'] = request.form["memo" + "_" + str(i + 1)]
                param_list[i]['exception'] = request.form["exception" + "_" + str(i + 1)]
                # invoke each job build
                job_name, build_number, queue_item = jenkins_api.build_job_from_info(server=jenkins_server,
                                                                                     task_id=task_number,
                                                                                     sys_id=param_list[i]['sys_id'],
                                                                                     git_tag=param_list[i]['gittag'],
                                                                                     opser=param_list[i]['opser'],
                                                                                     release_memo=param_list[i]['memo'],
                                                                                     release_exception=param_list[i][
                                                                                         'exception']
                                                                                     )
                # append returned job_name and build_number to list
                job_list.append((job_name, build_number, queue_item))
        # end for loops

        # log the job params.
        app.logger.info("the job param list is:{}".format(job_list))
        # make a resp and set job_list and task_number to cookies.
        resp = make_response(redirect("/build/query_batch_build_result"))
        resp.set_cookie('job_list', dumps(job_list), max_age=60 * 5)
        resp.set_cookie('task_number', dumps(task_number), max_age=60 * 5)
        resp.set_cookie('cmdb_done', dumps(False))
        return resp


@app.route("/build/query_batch_build_result", methods=["GET"])
def query_pre_batch_build():
    try:
        job_list = loads(request.cookies.get('job_list'))
        task_number = loads(request.cookies.get('task_number'))
        cmdb_done = loads(request.cookies.get('cmdb_done'))
        return render_template('/build/query_batch_build_result.html', job_list=job_list, task_number=task_number,
                               cmdb_done=cmdb_done)
    except Exception as e:
        app.logger.info("the request build info is expired!")
        redirect(url_for('index'))


@app.route("/build/pre_single_build", methods=["POST"])
def pre_single_build():
    """
    build a single job by given parameter on pre environment.

    :return:
    """
    # check login
    username = session.get("username")
    token = session.get("token")
    if username is None or token is None:
        return redirect(url_for('login'))
    # user existed
    else:
        try:
            jenkins_server = jenkins_api.connect_server(token, crowd_client)
            # build the job,ignore the need_to_build flag.
            task_number = request.form.get("task_number")
            sys_id = request.form.get("sysid")
            git_tag = request.form.get("gittag")
            opser = request.form.get("opser")
            memo = request.form.get("memo")
            exception = request.form.get("exception")
            # invoke job build from jenkins_api
            job_name, build_number, queue_item = jenkins_api.build_job_from_info(server=jenkins_server,
                                                                                 task_id=task_number,
                                                                                 sys_id=sys_id,
                                                                                 git_tag=git_tag,
                                                                                 opser=opser,
                                                                                 release_memo=memo,
                                                                                 release_exception=exception
                                                                                 )
            resp = {"code": RETURN_MSG_INFO.CODE_SUCCESS, "job_name": job_name, "build_number": build_number,
                    "queue_item": queue_item}
            return jsonify(resp)
        except Exception as e:
            resp = {"code": RETURN_MSG_INFO.CODE_FAILED, "msg": RETURN_MSG_INFO.MSG_FAILED_999001}
            return jsonify(resp)


@app.route("/build/query_build_state", methods=["GET"])
def query_build_states():
    """
    an api to query the job's build state. will need to check session user info first.
    the params are get from GET parameters.
    the response will be:
        1. {"job_name": job_name, "Status": "Waiting", "Response": "Waiting for queue."}
        2. {"job_name": job_name, "build_number": build_number, "Status": "Building", "Response": "{:2.2f}".format(progress)}
        3. {"job_name": job_name, "build_number": build_number, "Status": "Finished", "Response": result}

    :param job_name: the job's name
    :param queue_item: the queue item from build queue
    :param build_number: the build number the job allocated

    :return: a json response.
    """
    # check login
    username = session.get("username")
    token = session.get("token")
    if username is None or token is None:
        return redirect(url_for('login'))
    # user existed
    else:
        jenkins_server = jenkins_api.connect_server(token, crowd_client)
        job_name, queue_item, build_number = request.args.get("job_name"), request.args.get(
            "queue_item"), request.args.get("build_number")
        # invoke query build execution status, return the resp dict will be jsonify automatically
        resp = jenkins_api.query_build_execution(jenkins_server, job_name, int(queue_item), int(build_number))
        return jsonify(resp)


@app.route("/build/comment_on_redmine", methods=["POST"])
def insert_records_to_redmine():
    # check login
    username = session.get("username")
    token = session.get("token")
    if username is None or token is None:
        return redirect(url_for('login'))
    # user existed
    else:
        jenkins_server = jenkins_api.connect_server(token, crowd_client)
        task_number = request.form["task_number"]
        # invoke redmine insert job
        job_name, build_number, queue_item = jenkins_api.build_redmine_insert_job(server=jenkins_server,
                                                                                  task_id=task_number)
        resp = jenkins_api.query_build_execution(jenkins_server, job_name, int(queue_item), build_number)
        return jsonify(resp)


@app.route('/build/prod_build', methods=['GET'])
def prod_build():
    """
    render the input task number page.

    :return:
    """
    username = session.get("username")
    if username is None:
        return redirect(url_for('login'))
    else:
        return render_template('/build/prod_query_task_number.html')


@app.route("/build/prod_batch_build", methods=["GET"])
def query_prod_batch_build():
    task_number = int(request.args.get("task_number"))
    list_release_versions = gittag_extract.get_release_versions_to_build(task_number)
    return render_template('/build/prod_batch_build.html', task_number=task_number,
                           git_data=list_release_versions)


@app.route("/build/prod_batch_build", methods=["POST"])
def prod_batch_build():
    # check login
    username = session.get("username")
    token = session.get("token")
    if username is None or token is None:
        return redirect(url_for('login'))
    # user existed
    else:
        array_length = int(request.form['array_length'])
        task_number = int(request.form['task_number'])
        param_list = [{} for _ in range(array_length)]
        job_list = []
        jenkins_server = jenkins_api.connect_server(token, crowd_client)
        # batch submit jobs to jenkins
        for i in range(array_length):
            param_list[i]['preRelease_version'] = request.form["gittag" + "_" + str(i + 1)]
            param_list[i]['release_note'] = request.form["memo" + "_" + str(i + 1)]
            param_list[i]['release_exception'] = request.form["exception" + "_" + str(i + 1)]
            # invoke each job build
            job_name, build_number, queue_item = jenkins_api.build_cmdb_insert_job(server=jenkins_server,
                                                                                   pre_release_version=param_list[i][
                                                                                       'preRelease_version'],
                                                                                   release_note=param_list[i][
                                                                                       'release_note'],
                                                                                   release_exception=param_list[i][
                                                                                       'release_exception']
                                                                                   )
            # append returned job_name and build_number to list
            job_list.append((job_name, build_number, queue_item))
        # end for loops

        # log the job params.
        app.logger.info("the job param list is:{}".format(job_list))
        # make a resp and set job_list and task_number to cookies.
        resp = make_response(redirect("/build/query_batch_build_result"))
        resp.set_cookie('job_list', dumps(job_list), max_age=60 * 5)
        resp.set_cookie('task_number', dumps(task_number), max_age=60 * 5)
        resp.set_cookie('cmdb_done', dumps(True))
        return resp


"""
************************ Build phase related routes finish ****************************
"""

"""
************************ Deploy phase related routes start ****************************
"""


@app.route("/deploy/pre_deploy", methods=["GET"])
def pre_deploy():
    """
    render the input task number page.

    :return:
    """
    username = session.get("username")
    if username is None:
        return redirect(url_for('login'))
    else:
        return render_template('/deploy/pre_query_task_number.html')


@app.route('/deploy/pre_batch_deploy', methods=['GET'])
def render_pre_batch_deploy():
    task_number = int(request.args.get("task_number"))
    df_release_version = gittag_extract.get_release_version_to_deploy(task_number)

    return render_template('/deploy/pre_batch_deploy.html', task_number=task_number,
                           git_data=df_release_version.to_dict(orient="records"))


@app.route("/deploy/pre_single_deploy", methods=['POST'])
def pre_single_deploy():
    """
    build a single deploy job by given parameter on pre environment.

    :return:
    """
    # check login
    username = session.get("username")
    token = session.get("token")
    if username is None or token is None:
        return redirect(url_for('login'))
    # user existed
    else:
        try:
            prod_jenkins_server = jenkins_api.connect_server(token, crowd_client, env="prod")
            release_version = request.form['release_version']
            # invoke each job build
            job_name, build_number, queue_item = jenkins_api.build_pre_deploy_job_from_info(
                server=prod_jenkins_server, release_version=release_version)
            # invoke jenkins add deploy record job
            jenkins_api.build_add_deploy_record_job(server=prod_jenkins_server, release_version=release_version)
            resp = {"code": RETURN_MSG_INFO.CODE_SUCCESS, "job_name": job_name, "build_number": build_number,
                    "queue_item": queue_item}
            return jsonify(resp)
        except Exception as e:
            resp = {"code": RETURN_MSG_INFO.CODE_FAILED,
                    "msg": RETURN_MSG_INFO.MSG_FAILED_999001.format("release_version")}
            return jsonify(resp)


@app.route("/deploy/query_deploy_state", methods=["GET"])
def query_deploy_state():
    """
    an api to query the job's build state. will need to check session user info first.
    the params are get from GET parameters.
    the response will be:
        1. {"job_name": job_name, "Status": "Waiting", "Response": "Waiting for queue."}
        2. {"job_name": job_name, "build_number": build_number, "Status": "Building", "Response": "{:2.2f}".format(progress)}
        3. {"job_name": job_name, "build_number": build_number, "Status": "Finished", "Response": result}

    :param job_name: the job's name
    :param queue_item: the queue item from build queue
    :param build_number: the build number the job allocated

    :return: a json response.
    """
    # check login
    username = session.get("username")
    token = session.get("token")
    if username is None or token is None:
        return redirect(url_for('login'))
    # user existed
    else:
        prod_jenkins_server = jenkins_api.connect_server(token, crowd_client, env="prod")
        job_name, queue_item, build_number = request.args.get("job_name"), request.args.get(
            "queue_item"), request.args.get("build_number")
        # invoke query build execution status, return the resp dict will be jsonify automatically
        resp = jenkins_api.query_build_execution(prod_jenkins_server, job_name, int(queue_item), int(build_number))
        return jsonify(resp)


@app.route("/deploy/query_zeus_deploy_info", methods=["GET"])
def query_zeus_deploy_info():
    """
    query the deploy devices info in zeus system

    :parameter: sys_id. the sys id of system. e.g, "ld171"
    :parameter: env. the environment to query. e.g, "pre","prod"
    :return:
    """
    try:
        sys_id = request.args.get("sys_id")
        env = request.args.get("env")
        zeus_deploy_info = zeus_api.get_deploy_info_by_sysid(sys_id=sys_id, env=env)
        resp = {"code": RETURN_MSG_INFO.CODE_SUCCESS, "deploy_info": zeus_deploy_info}
        return jsonify(resp)
    except:
        resp = {"code": RETURN_MSG_INFO.CODE_FAILED,
                "msg": RETURN_MSG_INFO.MSG_FAILED_999001.format("sys_id")}
        return jsonify(resp)


@app.route("/deploy/prod_deploy")
def prod_deploy():
    """
    render the input task number page.

    :return:
    """
    username = session.get("username")
    if username is None:
        return redirect(url_for('login'))
    else:
        return render_template('/deploy/prod_query_task_number.html')


@app.route('/deploy/prod_batch_deploy', methods=['GET'])
def render_prod_batch_deploy():
    # check login
    username = session.get("username")
    token = session.get("token")
    if username is None or token is None:
        return redirect(url_for('login'))
    # user existed
    else:
        task_number = int(request.args.get("task_number"))
        df_release_version = gittag_extract.get_release_version_to_deploy(task_number, env="prod")
        prod_jenkins_server = jenkins_api.connect_server(token, crowd_client, env="prod")
        job_executable_list = jenkins_api.get_task_executable_job_list_async(prod_jenkins_server, task_number,
                                                                             df_release_version['sys_id'])
        df_release_version["executable_job_list"] = job_executable_list
        return render_template('/deploy/prod_batch_deploy.html', task_number=task_number,
                               git_data=df_release_version.to_dict(orient="records"))


@app.route("/deploy/prod_single_deploy", methods=['POST'])
def prod_single_deploy():
    """
    build a single deploy job by given parameter on PROD environment.

    :return:json
    """
    # check login
    username = session.get("username")
    token = session.get("token")
    if username is None or token is None:
        return redirect(url_for('login'))
    # user existed
    else:
        try:
            prod_jenkins_server = jenkins_api.connect_server(token, crowd_client, env="prod")
            job_name = request.form['job_name']
            release_version = request.form['release_version']
            job_name, build_number, queue_item = jenkins_api.build_prod_deploy_job_from_info(
                server=prod_jenkins_server, release_version=release_version, job_name=job_name)
            jenkins_api.build_add_deploy_record_job(server=prod_jenkins_server, release_version=release_version)
            resp = {"code": RETURN_MSG_INFO.CODE_SUCCESS, "job_name": job_name, "build_number": build_number,
                    "queue_item": queue_item}
            return jsonify(resp)
        except Exception as e:
            resp = {"code": RETURN_MSG_INFO.CODE_FAILED,
                    "msg": RETURN_MSG_INFO.MSG_FAILED_999001.format("release_version")}
            return jsonify(resp)


@app.errorhandler(Exception)
def handle_exception(exception):
    # pass through HTTP errors
    if isinstance(exception, HTTPException):
        return exception
    # now you're handling non-HTTP exceptions only
    return render_template("/error/500_generic.html", exception=exception), 500


@app.errorhandler(InternalServerError)
def handle_500(exception):
    original = getattr(exception, "original_exception", None)

    if original is None:
        # direct 500 error, such as abort(500)
        return render_template("/error/500.html"), 500

    # wrapped unhandled error
    return render_template("/error/500_generic.html", exception=original), 500


if __name__ == '__main__':
    running_env = "prod"
    try:
        port_number = int(sys.argv[1])
        running_env = str(sys.argv[2])
        print(port_number)
        private_log.info('Strategy_gateway', sys._getframe().f_code.co_name, 'starting port :' + str(port_number),
                         sys_id='')
    except Exception as exception:
        private_log.info('Strategy_gateway', sys._getframe().f_code.co_name, 'error :' + '请带上整数类型的端口号启动此程序', sys_id='')
        print('please start the server with port')
        sys.exit(1)
    if port_number <= 1024:
        private_log.info('Strategy_gateway', sys._getframe().f_code.co_name, 'error :' + '端口号请大于1024', sys_id='')
        sys.exit(1)
    private_log.info('Strategy_gateway', sys._getframe().f_code.co_name, 'started port :' + str(port_number),
                     sys_id='linux')
    # debug mode
    if running_env == "debug":
        app.debug = True
        app.run(host='0.0.0.0')
    else:
        addr, port = '0.0.0.0', port_number
        server = WSGIServer((addr, int(port)), app)
        server.serve_forever()
